/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#ifndef SHARED_DB_SQLPREPAREDSTATEMENT_H_
#define SHARED_DB_SQLPREPAREDSTATEMENT_H_

#if defined(_MSC_VER) && (_MSC_VER >= 1200)
# pragma once
#endif

#include <cstdint>
#include <exception>
#include <string>
#include <vector>
#include <sstream>
#include "Shared/Global_Macros.h"


namespace Shared {
namespace DB {
    class SqlConnection;

    ///////////////////////////////////////////////////////////////////////////
    union SqlStmtField
    {
        bool boolean;
        std::uint8_t ui8;
        std::int8_t i8;
        std::uint16_t ui16;
        std::int16_t i16;
        std::uint32_t ui32;
        std::int32_t i32;
        std::uint64_t ui64;
        std::int64_t i64;
        float f;
        double d;
    };

    enum SqlStmtFieldType
    {
        FIELD_BOOL,
        FIELD_UI8,
        FIELD_UI16,
        FIELD_UI32,
        FIELD_UI64,
        FIELD_I8,
        FIELD_I16,
        FIELD_I32,
        FIELD_I64,
        FIELD_FLOAT,
        FIELD_DOUBLE,
        FIELD_STRING,
        FIELD_NONE
    };


    ///////////////////////////////////////////////////////////////////////////
    class SqlStmtFieldData
    {
    public:
        SqlStmtFieldData() : _type(FIELD_NONE) { _binaryData.ui64 = 0; }
        ~SqlStmtFieldData() {}

        template<typename T>
        SqlStmtFieldData(T param) { set(param); }

        template<typename T1>
        void set(T1 param1);

        bool toBool() const { SHARED_ASSERT(_type == FIELD_BOOL); return _binaryData.ui8 != 0; }
        std::uint8_t toUint8() const { SHARED_ASSERT(_type == FIELD_UI8); return _binaryData.ui8; }
        std::int8_t toInt8() const { SHARED_ASSERT(_type == FIELD_I8); return _binaryData.i8; }
        std::uint16_t toUint16() const { SHARED_ASSERT(_type == FIELD_UI16); return _binaryData.ui16; }
        std::int16_t toInt16() const { SHARED_ASSERT(_type == FIELD_I16); return _binaryData.i16; }
        std::uint32_t toUint32() const { SHARED_ASSERT(_type == FIELD_UI32); return _binaryData.ui32; }
        std::int32_t toInt32() const { SHARED_ASSERT(_type == FIELD_I32); return _binaryData.i32; }
        std::uint64_t toUint64() const { SHARED_ASSERT(_type == FIELD_UI64); return _binaryData.ui64; }
        std::int64_t toInt64() const { SHARED_ASSERT(_type == FIELD_I64); return _binaryData.i64; }
        float toFloat() const { SHARED_ASSERT(_type == FIELD_FLOAT); return _binaryData.f; }
        double toDouble() const { SHARED_ASSERT(_type == FIELD_DOUBLE); return _binaryData.d; }
        const char* toStr() const { SHARED_ASSERT(_type == FIELD_STRING); return _stringData.c_str(); }

        SqlStmtFieldType type() const { return _type; }
        void* buff() const { return _type == FIELD_STRING ? (void*)_stringData.c_str() : (void*)&_binaryData; }
        size_t size() const
        {
            switch (_type)
            {
            case FIELD_NONE:    return 0;
            case FIELD_BOOL:    // return sizeof(bool);
            case FIELD_UI8:     return sizeof(std::uint8_t);
            case FIELD_UI16:    return sizeof(std::uint16_t);
            case FIELD_UI32:    return sizeof(std::uint32_t);
            case FIELD_UI64:    return sizeof(std::uint64_t);
            case FIELD_I8:      return sizeof(std::int8_t);
            case FIELD_I16:     return sizeof(std::int16_t);
            case FIELD_I32:     return sizeof(std::int32_t);
            case FIELD_I64:     return sizeof(std::int64_t);
            case FIELD_FLOAT:   return sizeof(float);
            case FIELD_DOUBLE:  return sizeof(double);
            case FIELD_STRING:  return _stringData.length();

            default:
                throw std::runtime_error("unrecognized type of SqlStmtFieldType obtained");
            }
        }

    private:
        SqlStmtFieldType _type;
        SqlStmtField _binaryData;
        std::string _stringData;
    };

    template<> inline void SqlStmtFieldData::set(bool val) { _type = FIELD_BOOL; _binaryData.ui8 = val; }
    template<> inline void SqlStmtFieldData::set(std::uint8_t val) { _type = FIELD_UI8; _binaryData.ui8 = val; }
    template<> inline void SqlStmtFieldData::set(std::int8_t val) { _type = FIELD_I8; _binaryData.i8 = val; }
    template<> inline void SqlStmtFieldData::set(std::uint16_t val) { _type = FIELD_UI16; _binaryData.ui16 = val; }
    template<> inline void SqlStmtFieldData::set(std::int16_t val) { _type = FIELD_I16; _binaryData.i16 = val; }
    template<> inline void SqlStmtFieldData::set(std::uint32_t val) { _type = FIELD_UI32; _binaryData.ui32 = val; }
    template<> inline void SqlStmtFieldData::set(std::int32_t val) { _type = FIELD_I32; _binaryData.i32 = val; }
    template<> inline void SqlStmtFieldData::set(std::uint64_t val) { _type = FIELD_UI64; _binaryData.ui64 = val; }
    template<> inline void SqlStmtFieldData::set(std::int64_t val) { _type = FIELD_I64; _binaryData.i64 = val; }
    template<> inline void SqlStmtFieldData::set(float val) { _type = FIELD_FLOAT; _binaryData.f = val; }
    template<> inline void SqlStmtFieldData::set(double val) { _type = FIELD_DOUBLE; _binaryData.d = val; }
    template<> inline void SqlStmtFieldData::set(const char* val) { _type = FIELD_STRING; _stringData = val; }


    ///////////////////////////////////////////////////////////////////////////
    class SqlStatement;
    class SqlStmtParameters
    {
    public:
        typedef ::std::vector<SqlStmtFieldData> ParameterContainer;
        explicit SqlStmtParameters(std::uint32_t nParams);

        ~SqlStmtParameters() {}
        ParameterContainer::size_type boundParams() const { return _params.size(); }
        void addParam(const SqlStmtFieldData& data) { _params.push_back(data); }
        void reset(const SqlStatement& stmt);
        void swap(SqlStmtParameters& obj);
        const ParameterContainer& params() const { return _params; }

    private:
        SqlStmtParameters& operator=(const SqlStmtParameters& obj);
        ParameterContainer _params;
    };


    ///////////////////////////////////////////////////////////////////////////
    class SqlStatementID
    {
    public:
        SqlStatementID() : _initialized(false) {}
        int id() const { return _index; }
        std::uint32_t arguments() const { return _arguments; }
        bool initialized() const { return _initialized; }

    private:
        friend class Database;
        void init(int nID, std::uint32_t nArgs) { _index = nID; _arguments = nArgs; _initialized = true; }

        int _index;
        std::uint32_t _arguments;
        bool _initialized;
    };


    ///////////////////////////////////////////////////////////////////////////
    class SqlStatement
    {
    public:
        ~SqlStatement() { delete _params; }

        SqlStatement(const SqlStatement& index) : _index(index._index), _db(index._db), _params(NULL)
        {
            if (index._params)
            { _params = new SqlStmtParameters(*(index._params)); }
        }

        SqlStatement& operator=(const SqlStatement& index);
        int id() const { return _index.id(); }
        std::uint32_t arguments() const { return _index.arguments(); }
        bool execute();
        bool directExecute();

        template<typename ParamType1>
        bool pexecute(ParamType1 param1)
        {
            arg(param1);
            return execute();
        }

        template<typename ParamType1, typename ParamType2>
        bool pexecute(ParamType1 param1, ParamType2 param2)
        {
            arg(param1);
            arg(param2);
            return execute();
        }

        template<typename ParamType1, typename ParamType2, typename ParamType3>
        bool pexecute(ParamType1 param1, ParamType2 param2, ParamType3 param3)
        {
            arg(param1);
            arg(param2);
            arg(param3);
            return execute();
        }

        template<typename ParamType1, typename ParamType2, typename ParamType3, typename ParamType4>
        bool pexecute(ParamType1 param1, ParamType2 param2, ParamType3 param3, ParamType4 param4)
        {
            arg(param1);
            arg(param2);
            arg(param3);
            arg(param4);
            return execute();
        }

        void addBool(bool var) { arg(var); }
        void addUInt8(::std::uint8_t var) { arg(var); }
        void addInt8(::std::int8_t var) { arg(var); }
        void addUInt16(::std::uint16_t var) { arg(var); }
        void addInt16(::std::int16_t var) { arg(var); }
        void addUInt32(::std::uint32_t var) { arg(var); }
        void addInt32(::std::int32_t var) { arg(var); }
        void addUInt64(::std::uint64_t var) { arg(var); }
        void addInt64(::std::int64_t var) { arg(var); }
        void addFloat(float var) { arg(var); }
        void addDouble(double var) { arg(var); }
        void addString(const char* var) { arg(var); }
        void addString(const std::string& var) { arg(var.c_str()); }
        void addString(std::ostringstream& ss) { arg(ss.str().c_str()); ss.str(std::string()); }

    protected:
        friend class Database;
        SqlStatement(const SqlStatementID& index, Database& db) 
            : _index(index), _db(&db), _params(NULL) {}

    private:
        SqlStmtParameters* get()
        {
            if (!_params)
            { _params = new SqlStmtParameters(arguments()); }

            return _params;
        }

        SqlStmtParameters* detach()
        {
            SqlStmtParameters* p = _params ? _params : new SqlStmtParameters(0);
            _params = NULL;
            return p;
        }

        template<typename ParamType>
        void arg(ParamType val)
        {
            SqlStmtParameters* p = get();
            p->addParam(SqlStmtFieldData(val));
        }


        SqlStatementID _index;
        Database* _db;
        SqlStmtParameters* _params;
    };


    ///////////////////////////////////////////////////////////////////////////
    class SqlPreparedStatement
    {
    public:
        virtual ~SqlPreparedStatement() {}
        bool isPrepared() const { return _prepared; }
        bool isQuery() const { return _query; }
        ::std::uint32_t params() const { return _params; }
        ::std::uint32_t columns() const { return isQuery() ? _columns : 0; }
        virtual bool prepare() = 0;
        virtual void bind(const SqlStmtParameters& holder) = 0;
        virtual bool execute() = 0;

    protected:
        SqlPreparedStatement(const std::string& fmt, SqlConnection& conn) :
             _params(0), _columns(0), _query(false),
                 _prepared(false), _format(fmt), _conn(conn)
             {}

             ::std::uint32_t _params;
             ::std::uint32_t _columns;
             bool _query;
             bool _prepared;
             std::string _format;
             SqlConnection& _conn;
    };


    ///////////////////////////////////////////////////////////////////////////
    class SqlPlainPreparedStatement : public SqlPreparedStatement
    {
    public:
        SqlPlainPreparedStatement(const std::string& fmt, SqlConnection& conn);
        ~SqlPlainPreparedStatement() {}
        virtual bool prepare() override { return true; }
        virtual void bind(const SqlStmtParameters& holder) override;
        virtual bool execute() override;

    protected:
        void dataToString(const SqlStmtFieldData& data, std::ostringstream& fmt);
        std::string _plainRequest;
    };

}}

#endif // SHARED_DB_SQLPREPAREDSTATEMENT_H_
